/* Copyright 2008 Bas van den Berg
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <string>

#include "yaffut.h"
#include "myassert.h"

#include "MudObject.h"
#include "MudMobile.h"
#include "Room.h"
#include "Error.h"

using namespace std;
using namespace Chronos;


class TestListener : public MudObjectListener {
public:
	TestListener() : obj(0) {}
	virtual bool handleObjectEvent(MudObject& object, MudObjectEvent event) {
    	switch(event) {
       	case OBJECT_DESTRUCTION:
			obj = &object;
    		return false;
        default:
			ASSERT_FAIL();
			break;
	    }
		return true;
	}
	const MudObject* obj;
};


struct MudObjectListenerTest {
	MudObjectListenerTest() : object(new MudObject("TestObject", "A small testObject.")) {
		object->addListener(listener1);
		object->addListener(listener2);
	}

	TestListener listener1;
	TestListener listener2;
	MudObject* object;
};


TEST(MudObjectListenerTest, testShouldConstruct) {
	delete object;
}


TEST(MudObjectListenerTest, testShouldNotifyListenerUponDestruction) {
	ASSERT_TRUE(listener1.obj == 0);
	delete object;
	ASSERT_TRUE(object == listener1.obj);
}


TEST(MudObjectListenerTest, testShouldNotNotifyRemovedListener) {
	ASSERT_TRUE(listener1.obj == 0);
	ASSERT_TRUE(listener2.obj == 0);
	object->removeListener(listener1);
	delete object;
	ASSERT_TRUE(listener1.obj == 0);
	ASSERT_TRUE(object == listener2.obj);
}


TEST(MudObjectListenerTest, testShouldThrowOnWrongAddOrRemoveListener) {
	try {
		object->addListener(listener1);
		ASSERT_FAIL();
	} catch (const MudError&) {}

	object->removeListener(listener1);
	try {
		object->removeListener(listener1);
		ASSERT_FAIL();
	} catch (const MudError& e) {}
	delete object;
}


namespace Chronos {
class MudObjectTestMobile : public MudMobile {
public:
	MudObjectTestMobile(const std::string& name_)
		: MudMobile(name_, "descr", 100, 2, 10, 50)
		, triggerCalled(false)
		, lastCause(0)
		, lastMsg("")
	{}

    virtual unsigned int doCombat() { return 0; }
    virtual void miss(MudMobile&) {}
    virtual bool takeDmg(MudMobile&, unsigned int) { return false; }

    virtual void handleTrigger(MudTriggerAction action, MudMobile* cause, const std::string& msg) {
		triggerCalled = true;
		lastAction = action;
		lastCause = cause;
		lastMsg = msg;
	}
	bool triggerCalled;
	MudTriggerAction lastAction;
	MudMobile* lastCause;
	std::string lastMsg;
};


class MudObjectTestObject : public MudObject {
public:
	MudObjectTestObject() : MudObject("object", "") {}
	virtual void handleTrigger(MudTriggerAction, MudMobile*, const std::string&) {

	}
	bool testTrigger(MudObjectTrigger trigger, MudMobile* cause) {
		return MudObject::activateTrigger(trigger, cause);
	}
};

}

struct MudObjectTriggerTest {
	MudObjectTriggerTest()
		: object(new MudObjectTestObject())
        , mobile1(new MudObjectTestMobile("mob1"))
        , mobile2(new MudObjectTestMobile("mob2"))
        , room("", "")
    {
        room.addObject(object);
		room.addObject(mobile1);
		room.addObject(mobile2);
    }

	MudObjectTestObject* object;
    MudObjectTestMobile* mobile1;
    MudObjectTestMobile* mobile2;
    Room room;
};


TEST(MudObjectTriggerTest, testShouldTrigger) {
	object->addTrigger(MUDEXIT_PASS, "mob2", true, ATTACK, "msg");
    ASSERT_TRUE(object->testTrigger(MUDEXIT_PASS, mobile1));
    ASSERT_TRUE(mobile2->triggerCalled);
	ASSERT_EQUAL(ATTACK, mobile2->lastAction);
	ASSERT_EQUAL(mobile1, mobile2->lastCause);
	ASSERT_STR_EQUAL("msg", mobile2->lastMsg);
}


TEST(MudObjectTriggerTest, testTriggeringWithoutTriggerActions) {     
    ASSERT_FALSE(object->testTrigger(MUDEXIT_PASS, mobile1));
    ASSERT_FALSE(mobile1->triggerCalled);
}


TEST(MudObjectTriggerTest, testShouldNotTriggeringOnDifferentTrigger) {
	object->addTrigger(MUDEXIT_PASS, "mob2", true, ATTACK, "msg");
    ASSERT_FALSE(object->testTrigger(MUDITEM_TRY_PICKUP, mobile1));
    ASSERT_FALSE(mobile2->triggerCalled);
}


TEST(MudObjectTriggerTest, testListenerNotInRoom) {
	object->addTrigger(MUDEXIT_PASS, "mob2", true, ATTACK, "msg");
	room.removeObject(mobile2);
    ASSERT_FALSE(object->testTrigger(MUDITEM_TRY_PICKUP, mobile1));
    ASSERT_FALSE(mobile2->triggerCalled);
	room.addObject(mobile2);
}


TEST(MudObjectTriggerTest, testShouldStackBlockResults) {
	object->addTrigger(MUDEXIT_PASS, "mob1", false, ATTACK, "msg");
	object->addTrigger(MUDEXIT_PASS, "mob2", true, ATTACK, "msg");
    ASSERT_TRUE(object->testTrigger(MUDEXIT_PASS, mobile1));
    ASSERT_TRUE(mobile1->triggerCalled);
    ASSERT_TRUE(mobile2->triggerCalled);
}


struct MudObjectAliasTest {
	MudObjectTestObject object;
};


TEST(MudObjectAliasTest, testAliases) {
	ASSERT_TRUE(object.matchesName("object"));
	ASSERT_FALSE(object.matchesName("alias1"));
	object.addAlias("alias1");
	ASSERT_TRUE(object.matchesName("alias1"));
}


TEST(MudObjectAliasTest, testCopyConstructorShouldCopyAliases) {
	object.addAlias("alias1");
	object.addAlias("alias2");
	MudObjectTestObject objectCopy(object);
	ASSERT_TRUE(objectCopy.matchesName("alias1"));
	ASSERT_TRUE(objectCopy.matchesName("alias2"));
}



